﻿using Agora.Rtc;
using DryIoc;
using HandyControl.Controls;
using Microsoft.VisualBasic.ApplicationServices;
using MyMeeting.Common;
using MyMeeting.Dtos;
using MyMeeting.Extensions;
using MyMeeting.Service;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MyMeeting.Common
{
    public class ShengGEngine : IEngine
    {
        public Dictionary<uint, VideoViual> DicMeetUsers { get; }

        public List<MeetRoom> MeetRooms { get; }
        private VideoFrameObserver videoFrameObserver = null;
        //VideoViual centerVideo;

        private Dictionary<string, VideoViual> DicCenterCtl;

        public IRtcEngineEx Rtc => rtc_engine_;
        public Action<VideoViual> EnableRemoteVideoEvent;
        public Action<VideoViual> EnableRemoteAudioEvent;
        public Action<VideoViual> JoinUserEvent;
        public Action<VideoViual> LeaveUserEvent;
        public Action<string, List<AudioVolumeInfo>> VolumeChangeEvent;
       
        public void AddCenter(string channel, VideoViual center)
        {
            DicCenterCtl[channel] = center;
        }

        public ShengGEngine()
        {
            DicCenterCtl = new Dictionary<string, VideoViual>();
            DicMeetUsers = new Dictionary<uint, VideoViual>();
            MeetRooms = new List<MeetRoom>();
        }

        internal override int Init(string appId)
        {
            int ret = -1;

            if (null == rtc_engine_)
            {
                rtc_engine_ = RtcEngine.CreateAgoraRtcEngineEx();
            }
            RtcEngineContext rtc_engine_ctx = new RtcEngineContext();
            rtc_engine_ctx.appId = appId;
            ret = rtc_engine_.Initialize(rtc_engine_ctx);
            ret = rtc_engine_.InitEventHandler(this);
            videoFrameObserver = new VideoFrameObserver(DicMeetUsers, DicCenterCtl);
            ret = rtc_engine_.RegisterVideoFrameObserver(videoFrameObserver);

            return ret;
        }
        
        public VideoViual OpenLocalVideo(string channelId, uint uid)
        {
            var centerVideo = GetCenter(channelId);
            if (centerVideo == null) return null;
            var ctl = FindVideoCtl(channelId, uid);
            int ret = -1;
            if (ctl.IsOpenVideo)
            {
                ret = Rtc.MuteLocalVideoStream(true);
                ret = Rtc.StopPreview();

                if (ret == 0)
                {
                    ctl.IsOpenVideo = false;
                    
                    ctl.ClearBackground();
                    if (centerVideo != null && centerVideo.Uid == ctl.Uid)
                    {

                        centerVideo.ClearBackground();
                    }
                }
            }
            else
            {
                ret = rtc_engine_.StartPreview(VIDEO_SOURCE_TYPE.VIDEO_SOURCE_CAMERA_PRIMARY);
                ret = Rtc.MuteLocalVideoStream(false);
                if (ret == 0)
                {
                    ctl.IsOpenVideo = true;
                }

            }
            return ctl;
        }
        public VideoViual FindVideoCtl(string channel, uint uid)
        {
            var findRoom = MeetRooms.Find(a => a.ChannelId == channel);
            if (findRoom == null || findRoom.DicMeetUsers == null) return null;
            var dic = findRoom.DicMeetUsers;
            if (dic.ContainsKey(uid))
                return dic[uid];
            return null;
        }
        public MeetRoom FindRoom(string channel)
        {
            return MeetRooms.Find(a => a.ChannelId == channel);
        }
        internal override int JoinChannel(string channelId, string token, uint uid)
        {
            var centerVideo = GetCenter(channelId);
            if (centerVideo == null) return -1;

            int ret = -1;
            if (null != Rtc)
            {
                Rtc.EnableVideo();
                Rtc.EnableAudio();
                ChannelMediaOptions options = new ChannelMediaOptions();
                options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
                options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
                ret = rtc_engine_.JoinChannel(token, channelId, uid, options);
                Rtc.MuteLocalVideoStream(true);
                Rtc.MuteLocalAudioStream(true);
            }

            var findRoom = MeetRooms.Find(a => a.ChannelId == channelId);
            if (findRoom == null)
            {
                findRoom = new MeetRoom() { ChannelId = channelId, Token = token, DicMeetUsers = new Dictionary<uint, VideoViual>() };
                centerVideo.Dispatcher.Invoke((Action)(() => {
                    VideoViual ctl = new VideoViual(centerVideo.PnlContianer, centerVideo, token, channelId, uid);
                    ctl.Width = 160;
                    ctl.Height = 90;
                    findRoom.DicMeetUsers[uid] = ctl;
                    ctl.ClearBackground();
                    DicMeetUsers[uid] = ctl;
                    ctl.Add();
                    ctl.SetImage();
                }));
               
                MeetRooms.Add(findRoom);
            }
            else
            {
                var dic = findRoom.DicMeetUsers;
                if (!dic.ContainsKey(uid))
                {
                    centerVideo.Dispatcher.Invoke((Action)(() =>
                    {
                        var ctl = new VideoViual(centerVideo.PnlContianer, centerVideo, token, channelId, uid);
                        ctl.Width = 160;
                        ctl.Height = 90;
                        dic[uid] = ctl;
                        dic[uid].Add();
                        ctl.SetImage();
                        dic[uid].ClearBackground();
                    }));
                 
                    DicMeetUsers[uid] = dic[uid];
                }
            }

            return ret;
        }
        internal override int UnInit()
        {
            {
                if (null != rtc_engine_)
                {
                    // Stop all renderer
                    this.StopAll();

                    // Dispose engine
                    rtc_engine_.Dispose();
                    rtc_engine_ = null;
                }

                return 0;
            }
        }
        public override void OnError(int err, string msg)
        {

            Debug.WriteLine($"{err},{msg}");
        }
        public override void OnJoinChannelSuccess(RtcConnection connection, int elapsed)
        {
            base.OnJoinChannelSuccess(connection, elapsed);
            this.Rtc.EnableAudioVolumeIndication(200, 3, false);
            var uid = PrismManager.CurrentUser.Id;//connection.localUid;
            var findRoom = FindRoom(PrismManager.CurrentUser.Channel);

            var centerVideo = GetCenter(connection.channelId);
            if (centerVideo == null) return;
            if (DicMeetUsers.ContainsKey(uid))
            {
                var ctl = DicMeetUsers[uid];
                ctl.IsJoined = true;
                if (uid != connection.localUid)
                {
                    ctl.Uid = connection.localUid;
                    centerVideo.Uid = ctl.Uid;
                    PrismManager.CurrentUser.Id = connection.localUid;
                    DicMeetUsers[ctl.Uid] = ctl;

                    DicMeetUsers.Remove(uid);

                    if (findRoom != null)
                    {
                        findRoom.DicMeetUsers[ctl.Uid] = ctl;
                        if (findRoom.DicMeetUsers.ContainsKey(uid))
                        {
                            findRoom.DicMeetUsers.Remove(uid);
                        }
                    }

                }

                JoinUserEvent?.Invoke(ctl);
            }
        }

        public override void OnUserJoined(RtcConnection connection, uint remoteUid, int elapsed)
        {
            base.OnUserJoined(connection, remoteUid, elapsed);

            var centerVideo = GetCenter(connection.channelId);
            if (centerVideo == null) return;
            var room = FindRoom(connection.channelId);
            if (room != null)
            {
                centerVideo.Dispatcher.Invoke((Action)(() =>
                {
                    VideoViual v = new VideoViual(centerVideo.PnlContianer, centerVideo, "", connection.channelId, remoteUid);
                    v.IsJoined = true;
                    v.Uid = remoteUid;
                    v.Width = 160;
                    v.Height = 90;
                    v.UserName = v.Uid + "用户";
                    DicMeetUsers[v.Uid] = v;
                    room.DicMeetUsers[v.Uid] = v;
                    v.Add();
                    v.SetImage();
                    JoinUserEvent?.Invoke(v);
                }));
            }

        }
        public override void OnLeaveChannel(RtcConnection connection, RtcStats stats)
        {
            base.OnLeaveChannel(connection, stats);
            DicCenterCtl.Remove(connection.channelId);
            var room = FindRoom(connection.channelId);
            if (DicMeetUsers.ContainsKey(connection.localUid))
            {
                DicMeetUsers[connection.localUid].DestroyRender();
                DicMeetUsers[connection.localUid].LiveChannel();
                LeaveUserEvent?.Invoke(DicMeetUsers[connection.localUid]);
                DicMeetUsers.Remove(connection.localUid);
            }
            if (room != null && room.DicMeetUsers.ContainsKey(connection.localUid))
            {
                room.DicMeetUsers[connection.localUid].LiveChannel();
                room.DicMeetUsers.Remove(connection.localUid);
            }
        }
        public override void OnUserOffline(RtcConnection connection, uint remoteUid, USER_OFFLINE_REASON_TYPE reason)
        {
            base.OnUserOffline(connection, remoteUid, reason);
            var room = FindRoom(connection.channelId);
            if (DicMeetUsers.ContainsKey(remoteUid))
            {
                DicMeetUsers[remoteUid].DestroyRender();
                DicMeetUsers[remoteUid].LiveChannel();
                LeaveUserEvent?.Invoke(DicMeetUsers[remoteUid]);
                DicMeetUsers.Remove(remoteUid);
            }
            if (room != null && room.DicMeetUsers.ContainsKey(remoteUid))
            {
                room.DicMeetUsers[remoteUid].LiveChannel();
                room.DicMeetUsers.Remove(remoteUid);
            }
        }
        public void StopAll()
        {
            DicMeetUsers.Values.ToList().ForEach(ctl =>
            {
                ctl.DestroyRender();
            });
        }

        private VideoViual GetCenter(string channel)
        {
            if (DicCenterCtl.ContainsKey(channel))
            {
                return DicCenterCtl[channel];
            }
            else
            {
                return null;
            }
        }

        public override void OnUserMuteVideo(RtcConnection connection, uint remoteUid, bool enabled)
        {
            var findRoom = FindRoom(connection.channelId);

            if (findRoom == null) return;
            var dic = findRoom.DicMeetUsers;
            if (dic.ContainsKey(remoteUid))
            {
                var ctl = dic[remoteUid];
                ctl.IsOpenVideo = !enabled;
                ctl.ClearBackground();
                if (ctl.VCenter != null && ctl.VCenter.Uid == ctl.Uid)
                {
                    ctl.VCenter.ClearBackground();
                }
                EnableRemoteVideoEvent?.Invoke(ctl);
            }
        }
        public override void OnUserMuteAudio(RtcConnection connection, uint remoteUid, bool muted)
        {
            base.OnUserMuteAudio(connection, remoteUid, muted);
            var findRoom = FindRoom(connection.channelId);

            if (findRoom == null) return;
            var dic = findRoom.DicMeetUsers;
            if (dic.ContainsKey(remoteUid))
            {
                var ctl = dic[remoteUid];
                ctl.IsOpenAudio = !muted;

                EnableRemoteAudioEvent?.Invoke(ctl);
            }
        }

        internal override int LeaveChannel()
        {
            int ret = -1;
            if (null != Rtc)
            {
                ret = Rtc.StopPreview();          
                ret = Rtc.DisableVideo();           
                Rtc.MuteLocalVideoStream(true);
                ret = Rtc.LeaveChannel();
                var uid = PrismManager.CurrentUser.Id;
            }
            return ret;
        }

       public override void OnAudioVolumeIndication(RtcConnection connection, AudioVolumeInfo[] speakers, uint speakerNumber, int totalVolume)
        {
            base.OnAudioVolumeIndication(connection,speakers,speakerNumber,totalVolume);
            if (speakerNumber <= 0) return;
            var center = FindCenter(connection.channelId);
            for(var i=0;i<speakerNumber;i++)
            {
                var audioInfo = speakers[i];
                if(audioInfo.uid==0)
                {
                    audioInfo.uid =connection.localUid;
                }
                var viual = FindVideoCtl(connection.channelId, audioInfo.uid);
                if (viual != null)
                {
                    viual.Volume = audioInfo.volume;
                }
                if(center!=null&&center.Uid==audioInfo.uid)
                {
                    center.Volume = audioInfo.volume;
                }
            }
            VolumeChangeEvent?.Invoke(connection.channelId, new List<AudioVolumeInfo>(speakers));
        }

       public VideoViual FindCenter(string channel)
        {
            if(DicCenterCtl.ContainsKey(channel))
            {
                return DicCenterCtl[channel];
            }
            return null;
        }

        internal VideoViual OpenLocalAudio(string channelId, uint uid)
        {
            var ctl = FindVideoCtl(channelId, uid);
            int ret = -1;
            if (ctl.IsOpenAudio)
            {
               
                ret = Rtc.PauseAudio();
                ret = Rtc.MuteLocalAudioStream(true);
                if (ret == 0)
                {
                    ctl.IsOpenAudio = false;
                }
            }
            else
            {
                Rtc.ResumeAudio();
                ret = Rtc.MuteLocalAudioStream(false);
                if (ret == 0)
                {
                    ctl.IsOpenAudio = true;
                }

            }
            return ctl;
        }
       
        internal VideoViual StartShareScreen()
        {
            var sCaptureSourceInfo = Rtc.GetScreenCaptureSources(new SIZE(300, 300), new SIZE(30, 30), true);
            var ret = -1;
            if (sCaptureSourceInfo.Length <= 0)
            {
                return null;
            }
            var source = sCaptureSourceInfo[0];
            var parameters = new Agora.Rtc.ScreenCaptureParameters()
            {
                bitrate = 0,
                frameRate = 15,
                enableHighLight = true,
                windowFocus = true,
                dimensions = new Agora.Rtc.VideoDimensions(1920, 1080)
            };
            if (source.type == ScreenCaptureSourceType.ScreenCaptureSourceType_Screen)
            {
                ret = Rtc.StartScreenCaptureByDisplayId(0,
                    new Rectangle(0, 0, 1920, 1080),
                    parameters);

            }
            if (ret != 0) return null;
            var user = PrismManager.CurrentUser;
            // Mute video stream of screenshare connection coz we do not need to recv again
            ret = Rtc.MuteRemoteVideoStream(user.Id, true);

            // Mute audio stream of screenshare connection coz we do not need to recv again
            ret = Rtc.MuteRemoteAudioStream(user.Id, true);

            // Join channel with a new connection and disable auto subscribe remote streams
            ChannelMediaOptions options = new ChannelMediaOptions();
            options.channelProfile.SetValue(CHANNEL_PROFILE_TYPE.CHANNEL_PROFILE_LIVE_BROADCASTING);
            options.clientRoleType.SetValue(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
            options.publishCameraTrack.SetValue(false);
            options.publishMicrophoneTrack.SetValue(false);
            options.autoSubscribeAudio.SetValue(true);
            options.autoSubscribeVideo.SetValue(true);
            options.publishScreenTrack.SetValue(true);
            var config = new ScreenCaptureConfiguration();
            RtcConnection conn = new RtcConnection();
            conn.channelId = user.Channel;
            conn.localUid = user.Id;

            ret = Rtc.JoinChannelEx(user.Token, conn, options);
            return DicMeetUsers[user.Id];
        }
    }

    // override if need
    internal class VideoFrameObserver : IVideoFrameObserver
    {
        Dictionary<uint, VideoViual> DicMeetUsers;
        Dictionary<string, VideoViual> DicCenterCtl;

        public VideoFrameObserver(Dictionary<uint, VideoViual> users, VideoViual center)
        {
            DicMeetUsers = users;
        }

        public VideoFrameObserver(Dictionary<uint, VideoViual> users, Dictionary<string, VideoViual> center)
        {
            DicMeetUsers = users;
            DicCenterCtl = center;
        }

        private VideoViual GetCenter(string channel)
        {
            if (DicCenterCtl.ContainsKey(channel))
            {
                return DicCenterCtl[channel];
            }
            else
            {
                return null;
            }
        }
        public override bool OnPreEncodeVideoFrame(VIDEO_SOURCE_TYPE sourceType, VideoFrame videoFrame)
        {
            var user = PrismManager.CurrentUser;

            VideoViual centerVideo = GetCenter(user.Channel);
            if (centerVideo == null) return false;
            if (DicMeetUsers.ContainsKey(user.Id))
            {
                var ctl = DicMeetUsers[user.Id];
                if (sourceType == VIDEO_SOURCE_TYPE.VIDEO_SOURCE_SCREEN || sourceType == VIDEO_SOURCE_TYPE.VIDEO_SOURCE_SCREEN_PRIMARY)
                {
                    ctl.DeliverFrame(videoFrame);
                    if (centerVideo.Uid == user.Id)
                    {
                        centerVideo.DeliverFrame(videoFrame);
                    }
                    return true;
                }
                if (ctl.IsOpenVideo&&user.isShareScreen==false)
                    ctl.DeliverFrame(videoFrame);
            }

            if (centerVideo.Uid == user.Id)
            {
                if (centerVideo != null && centerVideo.IsOpenVideo && user.isShareScreen == false)
                    centerVideo.DeliverFrame(videoFrame);
            }
            return true;
        }

        public override bool OnRenderVideoFrame(string channelId, uint remoteUid, VideoFrame videoFrame)
        {
            VideoViual centerVideo = GetCenter(channelId);
            if (centerVideo == null) return false;

            if (DicMeetUsers.ContainsKey(remoteUid))
            {
                var ctl = DicMeetUsers[remoteUid];
                if (ctl.IsOpenVideo)
                {
                    DicMeetUsers[remoteUid].DeliverFrame(videoFrame);

                }
            }
            if (remoteUid == centerVideo.Uid)
            {
                if (centerVideo != null && centerVideo.IsOpenVideo)
                {
                    centerVideo.DeliverFrame(videoFrame);
                }
            }
            return true;
        }

        public override VIDEO_OBSERVER_FRAME_TYPE GetVideoFormatPreference()
        {
            return VIDEO_OBSERVER_FRAME_TYPE.FRAME_TYPE_BGRA;
        }

        public override VIDEO_OBSERVER_POSITION GetObservedFramePosition()
        {
            return VIDEO_OBSERVER_POSITION.POSITION_PRE_RENDERER | VIDEO_OBSERVER_POSITION.POSITION_PRE_ENCODER;
        }
    }
}
